package de.jmda.mview.fx.node.typeshape;

import javafx.scene.image.Image;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import de.jmda.mproc.ProcessingUtilities;
import de.jmda.mview.typeshape.DisplayOptionsModelUtil;
import de.jmda.util.StringUtil;
import de.jmda.util.gui.fx.ImageFactory;

public class ElementData<T extends Element>
{
//	private final static Logger LOGGER = Logger.getLogger(ElementData.class);

	private T element;
	private DisplayOptionsModelUtil displayOptionsModelUtil;

	private Image image;
	private String type;
	private String name;
	
	public ElementData(
			T element,
			DisplayOptionsModelUtil displayOptionsModelUtil)
	{
		super();

		this.element = element;
		this.displayOptionsModelUtil = displayOptionsModelUtil;

		image = ImageFactory.getImage(element, "png");
		type = initType();
		name = initName();
	}
	
	public T getElement()
	{
		return element;
	}

	public Image getImage()
  {
  	return image;
  }

	public String getType()
  {
  	return type;
  }

	public String getName()
  {
  	return name;
  }

	private String initType()
  {
		if (ProcessingUtilities.isPrimitiveKind(element))
		{
			return element.asType().toString();
		}
		else
		{
			if (element instanceof VariableElement)
			{
				// TODO handle array types
				if (displayOptionsModelUtil.displayFieldTypeQualified())
				{
					return
							ProcessingUtilities.asTypeElement(element.asType())
							                   .getQualifiedName().toString();
				}
				else
				{
					return
							ProcessingUtilities.asTypeElement(element.asType())
							                   .getSimpleName().toString();
				}
			}
			else if (element instanceof ExecutableElement)
			{
				TypeMirror typeMirrorOfReturnType =
						ProcessingUtilities.asExecutableElement(element).getReturnType();
				
				TypeKind typeKind = typeMirrorOfReturnType.getKind();

				if (typeKind == TypeKind.DECLARED)
				{
					if (displayOptionsModelUtil.displayMethodTypeQualified())
					{
						return
								ProcessingUtilities.asTypeElement(typeMirrorOfReturnType)
								                   .getQualifiedName().toString();
					}
					else
					{
						return
								ProcessingUtilities.asTypeElement(typeMirrorOfReturnType)
								                   .getSimpleName().toString();
					}
				}
				else if (typeKind == TypeKind.ARRAY)
				{
					ArrayType arrayType =
							ProcessingUtilities.asArrayType(typeMirrorOfReturnType);
					
					TypeMirror arrayTypeMirror = arrayType.getComponentType();
					
					TypeKind arrayTypeKind = arrayTypeMirror.getKind();

					if (arrayTypeKind == TypeKind.DECLARED)
					{
						TypeElement typeElement =
								ProcessingUtilities.asTypeElement(arrayTypeMirror);

						if (displayOptionsModelUtil.displayMethodParameterQualified())
						{
							return typeElement.getQualifiedName().toString() + "[]";
						}
						else
						{
							return typeElement.getSimpleName().toString() + "[]";
						}
					}
					else
					{
						return typeMirrorOfReturnType.toString();
					}
				}
				
				return typeMirrorOfReturnType.getKind().name().toLowerCase();
			}
			else if (element instanceof TypeElement)
			{
				// it is likely that element is an annotation, we already have the name
				// so return empty string
				return "";
			}
			else
			{
				return
						"unexpected element type " + element.asType().toString() +
						", classname " + element.getClass().getName();
			}
		}
  }

	private String initName()
  {
		if (element instanceof TypeElement)
		{
			// element is likely an annotation, so use the same display options as for
			// type name
			if (displayOptionsModelUtil.displayTypeNameQualified())
			{
				return
						ProcessingUtilities.asTypeElement(
								element.asType()).getQualifiedName().toString();
			}
			else
			{
				return
						ProcessingUtilities.asTypeElement(
								element.asType()).getSimpleName().toString();
			}
		}
		else if (element instanceof ExecutableElement)
		{
			return element.getSimpleName() + "(" + getMethodParameters() + ")";
		}

		return element.getSimpleName().toString();
  }

	private String getMethodParameters()
  {
		StringBuffer result = new StringBuffer();
		
		ExecutableElement executableElement =
				ProcessingUtilities.asExecutableElement(element);

		for (VariableElement variableElement : executableElement.getParameters())
		{
			result.append(getMethodParameter(variableElement) + ", ");
		}

		StringUtil.rTrimChars(result, ", ");

		return result.toString();
  }
	
	private String getMethodParameter(VariableElement variableElement)
	{
		String result;
		
		TypeMirror typeMirror = variableElement.asType();

		TypeKind typeKind = typeMirror.getKind();
		
		if (typeKind == TypeKind.DECLARED)
		{
			TypeElement typeElement = ProcessingUtilities.asTypeElement(typeMirror);

			if (displayOptionsModelUtil.displayMethodParameterQualified())
			{
				result = typeElement.getQualifiedName().toString();
			}
			else
			{
				result = typeElement.getSimpleName().toString();
			}
		}
		else if (typeKind == TypeKind.ARRAY)
		{
			ArrayType arrayType = ProcessingUtilities.asArrayType(typeMirror);
			
			TypeMirror arrayTypeMirror = arrayType.getComponentType();
			
			TypeKind arrayTypeKind = arrayTypeMirror.getKind();

			if (arrayTypeKind == TypeKind.DECLARED)
			{
				TypeElement typeElement =
						ProcessingUtilities.asTypeElement(arrayTypeMirror);

				if (displayOptionsModelUtil.displayMethodParameterQualified())
				{
					result = typeElement.getQualifiedName().toString() + "[]";
				}
				else
				{
					result = typeElement.getSimpleName().toString() + "[]";
				}
			}
			else
			{
				result = typeMirror.toString();
			}
		}
		else
		{
			result = typeMirror.toString();
//			result =
//					"unexpected type kind [" + typeKind + "] " +
//					"for type mirror [" + typeMirror + "]";
		}
		
		return result + " " + variableElement.getSimpleName();
	}
}